package cn.jisol.ngame.rpc;

import cn.hutool.core.annotation.AnnotationUtil;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import cn.jisol.ngame.client.NClient;
import cn.jisol.ngame.proto.message.NGameMessageOuterClass.*;
import cn.jisol.ngame.rpc.action.NBackAsync;
import cn.jisol.ngame.rpc.mode.uid.NUIDMode;
import cn.jisol.ngame.util.TypeUtil;
import cn.jisol.ngame.util.spring.SpringBeanUtils;
import com.alibaba.fastjson.JSON;
import com.google.protobuf.Any;
import com.google.protobuf.ByteString;
import com.google.protobuf.InvalidProtocolBufferException;
import com.google.protobuf.Message;
import org.springframework.util.StringUtils;

import java.lang.reflect.*;
import java.util.*;

public class NGameRPC {
    
    private static int receId = 1;

    private final static Map<Class<?>, ArrayList<Method>> vRPC = new HashMap<>();
    
    //网络回调
    public final static Map<Integer, NBackAsync<?>> vBack = new HashMap<>();

    public static void invoke(NClient client,NGameMessage nGameMessage,RunNewObject runNewObject) throws InvalidProtocolBufferException {

        Class<?> rClass = null;
        Method rMethod = null;
    
    
        HashMap<Object, Object> message = new HashMap<>();
    
        if(nGameMessage.getMessage().getTypeUrl().length() == 0){
            String msg = nGameMessage.getMessage().getValue().toStringUtf8();
            if(Objects.nonNull(msg) && msg.length() > 0)
                message = JSONUtil.toBean(msg,HashMap.class);
        }

        //默认模式 通过Action Event 找到方法
        if(!StringUtils.isEmpty(nGameMessage.getAction()) && !StringUtils.isEmpty(nGameMessage.getEvent())){
            for(Class<?> iClass : vRPC.keySet()){
                if(nGameMessage.getAction().equals(iClass.getSimpleName())){
                    rClass = iClass;
                    for(Method iMethod : vRPC.get(iClass)){
                        if(iMethod.getName().equals(nGameMessage.getEvent()))
                            rMethod = iMethod;
                    }
                }
            }
        } //UID模式 通过 UID 找到方法
        else if(nGameMessage.getUid() != 0){
            for(Class<?> iClass : vRPC.keySet()){
                for(Method iMethod : vRPC.get(iClass)){
                    NUIDMode uid = null;
                    if(Objects.nonNull(uid = AnnotationUtil.getAnnotation(iMethod,NUIDMode.class)) && uid.value() == nGameMessage.getUid()){
                        rClass = iClass;
                        rMethod = iMethod;
                    }
                }
            }
        } //回调方法 通过回调ID 找到 回调 进行调用
        else if(nGameMessage.getSReceId() != 0 && Objects.nonNull(vBack.get(nGameMessage.getSReceId()))){

            NBackAsync<?> nBackAsync = vBack.get(nGameMessage.getSReceId());
            if(message.get("rece").getClass().equals(JSONObject.class)){
                nBackAsync.emit(((JSONObject)message.get("rece")).toBean(nBackAsync.getRType()));
            }else{
                nBackAsync.emit(NGameRPC.toBean(nBackAsync.getRType(),message.get("rece")));
            }
            
        }

        if(Objects.isNull(rClass) || Objects.isNull(rMethod)) return;

        //接收的参数类型
        Parameter[] parameters = rMethod.getParameters();

        Object[] param = new Object[parameters.length];


        for (int i = 0; i < parameters.length; i++) {

            NRPCParam nRPCParam;

            //判断是否存在 NRPCParam 注解
            if(Objects.nonNull(nRPCParam = AnnotationUtil.getAnnotation(parameters[i],NRPCParam.class))){
                try {
                    if(message.get(nRPCParam.value()).getClass().equals(JSONObject.class)){
                        param[i] = ((JSONObject)message.get(nRPCParam.value())).toBean(parameters[i].getType());
//                        System.out.println(parameters[i].getType().toString()+TypeUtil.getActualTypeArgument(parameters[i].getType()));
//                        System.out.println(parameters[i].getParameterizedType());
                        
                        try{
    
                            ParameterizedType parameterizedType = (ParameterizedType) parameters[i].getParameterizedType();
                            Type actualTypeArgument = parameterizedType.getActualTypeArguments()[0];
    
                            Field[] declaredFields = param[i].getClass().getDeclaredFields();
    
                            for (Field declaredField : declaredFields) {
                                declaredField.setAccessible(true);
                                if (JSONObject.class.isAssignableFrom(declaredField.get(param[i]).getClass())){
                                    try{
                                        declaredField.set(param[i], ((JSONObject) declaredField.get(param[i])).toBean(actualTypeArgument));
                                    }catch (Exception ignored){}
                                }
                                if (List.class.isAssignableFrom(declaredField.get(param[i]).getClass())){
                                    List values = ((List) declaredField.get(param[i]));
                                    for (int i1 = 0; i1 < values.size(); i1++) {
                                        if (JSONObject.class.isAssignableFrom(values.get(i1).getClass())){
                                            try{
                                                values.set(i1,((JSONObject)values.get(i1)).toBean(actualTypeArgument));
                                            }catch (Exception ignored){}
                                        }
                                    }
                                }
                            }
                            
                        }catch (Exception ignored){
                        }
                        
//                        for (int i1 = 0; i1 < declaredFields.length; i1++) {
//                            if(declaredFields[i1] instanceof Object){
//                                declaredFields[i1].set(param[i],);
//                            }
//                        }
    
                    }else{
                        param[i] = message.get(nRPCParam.value());
                    }
                }catch (NullPointerException e){
                    System.out.println("未找到 RPC 参数:"+nRPCParam.value());
                }
            }else if(NClient.class.equals(parameters[i].getType()) || NClient.class.isAssignableFrom(parameters[i].getType()) ){ //判断类型是否是Client
                param[i] = client;
            }else if(Message.class.isAssignableFrom(parameters[i].getType())){
                if(nGameMessage.getMessage().getTypeUrl().length() > 0)
                    param[i] = nGameMessage.getMessage().unpack((Class<Message>)parameters[i].getType());
            }else if(NGameMessage.class.equals(parameters[i].getType())){ //判断类型是否是NGameMessage
                param[i] = nGameMessage;
            }

        }

        try {
            Object rData = rMethod.invoke(runNewObject.run(rClass),param);
            
            if (Objects.isNull(rData)) return;
            
            //如果返回异步回调则等待回调结果在发送
            if (rData.getClass().isAssignableFrom(NBackAsync.class)){
    
                NBackAsync<?> nBackAsync = (NBackAsync<?>)rData;
                nBackAsync.async(rdata -> {
                    String data = JSON.toJSONString(rdata);
                    client.onSend(
                        NGameMessage.newBuilder()
                            .setReceId(nGameMessage.getReceId())
                            .setMessage(Any.newBuilder().setValue(ByteString.copyFromUtf8(data)).build())
                            .build()
                    );
                });
            
            }else{
                //正常返回
                String data = JSON.toJSONString(rData);
                if(nGameMessage.getReceId() > 0){
                    client.onSend(
                        NGameMessage.newBuilder()
                            .setReceId(nGameMessage.getReceId())
                            .setMessage(Any.newBuilder().setValue(ByteString.copyFromUtf8(data)).build())
                            .build()
                    );
                }
            }
            
        } catch (Exception e) {
            e.printStackTrace();
        }

    }
    
    public static <T> T toBean(Type type,Object data) {
        return (T) ((Class)type).cast(data);
    }
    
    public static Integer addReceId(){
        return receId+=1;
    }

    public static void invoke(NClient client,NGameMessage nGameMessage) throws InvalidProtocolBufferException {
        invoke(client,nGameMessage, SpringBeanUtils::getBean);
    }

    public static void addRPCMethod(Class<?> vClass,Method vMethod){
        if(Objects.isNull(vRPC.get(vClass))){
            vRPC.put(vClass,new ArrayList<>());
        }
        vRPC.get(vClass).add(vMethod);
    }

    public static int lClassSize(){
        return vRPC.size();
    }

    public interface RunNewObject {
        public Object run(Class<?> vClass) throws IllegalAccessException, InstantiationException;
    }

}
